/*
 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.org.apache.xalan.internal.xsltc.trax;

import java.io.IOException;
import java.util.Iterator;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.ext.Locator2;
import com.sun.org.apache.xalan.internal.xsltc.dom.SAXImpl;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.ProcessingInstruction;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.events.StartDocument;


/**
 * @author Suresh Kumar
 * @author Sunitha Reddy
 * @since 1.6
 */
public class StAXEvent2SAX implements XMLReader, Locator {

  //private final static String EMPTYSTRING = "";
  //private static final String XMLNS_PREFIX = "xmlns";

  // StAX event source
  private final XMLEventReader staxEventReader;

  //private Node _dom = null;
  private ContentHandler _sax = null;
  private LexicalHandler _lex = null;
  private SAXImpl _saxImpl = null;
  //private Hashtable _nsPrefixes = new Hashtable();
  private String version = null;
  private String encoding = null;


  public StAXEvent2SAX(XMLEventReader staxCore) {
    staxEventReader = staxCore;
  }

  public ContentHandler getContentHandler() {
    return _sax;
  }

  public void setContentHandler(ContentHandler handler) throws
      NullPointerException {
    _sax = handler;
    if (handler instanceof LexicalHandler) {
      _lex = (LexicalHandler) handler;
    }

    if (handler instanceof SAXImpl) {
      _saxImpl = (SAXImpl) handler;
    }
  }


  public void parse(InputSource unused) throws IOException, SAXException {
    try {
      bridge();
    } catch (XMLStreamException e) {
      throw new SAXException(e);
    }
  }


  //Main Work Starts Here.
  public void parse() throws IOException, SAXException, XMLStreamException {
    bridge();
  }


    /*  public void parse() throws IOException, SAXException {
        if (_dom != null) {
            boolean isIncomplete =
                (_dom.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE);

            if (isIncomplete) {
                _sax.startDocument();
                parse(_dom);
                _sax.endDocument();
            }
            else {
                parse(_dom);
            }
        }
    }
    */

  /*
   * @see StAXReaderToContentHandler#bridge()
   */
  private void bridge() throws XMLStreamException {

    try {
      // remembers the nest level of elements to know when we are done.
      int depth = 0;
      boolean startedAtDocument = false;

      XMLEvent event = staxEventReader.peek();

      if (!event.isStartDocument() && !event.isStartElement()) {
        throw new IllegalStateException();
      }

      if (event.getEventType() == XMLStreamConstants.START_DOCUMENT) {
        startedAtDocument = true;
        version = ((StartDocument) event).getVersion();
        if (((StartDocument) event).encodingSet()) {
          encoding = ((StartDocument) event).getCharacterEncodingScheme();
        }
        event = staxEventReader.nextEvent(); // that gets the one we peeked at
        event = staxEventReader.nextEvent(); // that really gets the next one
      }

      handleStartDocument(event);

      // Handle the prolog: http://www.w3.org/TR/REC-xml/#NT-prolog
      while (event.getEventType() != XMLStreamConstants.START_ELEMENT) {
        switch (event.getEventType()) {
          case XMLStreamConstants.CHARACTERS:
            handleCharacters(event.asCharacters());
            break;
          case XMLStreamConstants.PROCESSING_INSTRUCTION:
            handlePI((ProcessingInstruction) event);
            break;
          case XMLStreamConstants.COMMENT:
            handleComment();
            break;
          case XMLStreamConstants.DTD:
            handleDTD();
            break;
          case XMLStreamConstants.SPACE:
            handleSpace();
            break;
          default:
            throw new InternalError("processing prolog event: " + event);
        }
        event = staxEventReader.nextEvent();
      }

      // Process the (document) element
      do {
        // These are all of the events listed in the javadoc for
        // XMLEvent.
        // The spec only really describes 11 of them.
        switch (event.getEventType()) {
          case XMLStreamConstants.START_ELEMENT:
            depth++;
            handleStartElement(event.asStartElement());
            break;
          case XMLStreamConstants.END_ELEMENT:
            handleEndElement(event.asEndElement());
            depth--;
            break;
          case XMLStreamConstants.CHARACTERS:
            handleCharacters(event.asCharacters());
            break;
          case XMLStreamConstants.ENTITY_REFERENCE:
            handleEntityReference();
            break;
          case XMLStreamConstants.PROCESSING_INSTRUCTION:
            handlePI((ProcessingInstruction) event);
            break;
          case XMLStreamConstants.COMMENT:
            handleComment();
            break;
          case XMLStreamConstants.DTD:
            handleDTD();
            break;
          case XMLStreamConstants.ATTRIBUTE:
            handleAttribute();
            break;
          case XMLStreamConstants.NAMESPACE:
            handleNamespace();
            break;
          case XMLStreamConstants.CDATA:
            handleCDATA();
            break;
          case XMLStreamConstants.ENTITY_DECLARATION:
            handleEntityDecl();
            break;
          case XMLStreamConstants.NOTATION_DECLARATION:
            handleNotationDecl();
            break;
          case XMLStreamConstants.SPACE:
            handleSpace();
            break;
          default:
            throw new InternalError("processing event: " + event);
        }

        event = staxEventReader.nextEvent();
      } while (depth != 0);

      if (startedAtDocument) {
        // Handle the Misc (http://www.w3.org/TR/REC-xml/#NT-Misc) that can follow the document element
        while (event.getEventType() != XMLStreamConstants.END_DOCUMENT) {
          switch (event.getEventType()) {
            case XMLStreamConstants.CHARACTERS:
              handleCharacters(event.asCharacters());
              break;
            case XMLStreamConstants.PROCESSING_INSTRUCTION:
              handlePI((ProcessingInstruction) event);
              break;
            case XMLStreamConstants.COMMENT:
              handleComment();
              break;
            case XMLStreamConstants.SPACE:
              handleSpace();
              break;
            default:
              throw new InternalError("processing misc event after document element: " + event);
          }
          event = staxEventReader.nextEvent();
        }
      }

      handleEndDocument();
    } catch (SAXException e) {
      throw new XMLStreamException(e);
    }
  }


  private void handleEndDocument() throws SAXException {
    _sax.endDocument();
  }

  private void handleStartDocument(final XMLEvent event) throws SAXException {
    _sax.setDocumentLocator(new Locator2() {
      public int getColumnNumber() {
        return event.getLocation().getColumnNumber();
      }

      public int getLineNumber() {
        return event.getLocation().getLineNumber();
      }

      public String getPublicId() {
        return event.getLocation().getPublicId();
      }

      public String getSystemId() {
        return event.getLocation().getSystemId();
      }

      public String getXMLVersion() {
        return version;
      }

      public String getEncoding() {
        return encoding;
      }

    });
    _sax.startDocument();
  }

  private void handlePI(ProcessingInstruction event)
      throws XMLStreamException {
    try {
      _sax.processingInstruction(
          event.getTarget(),
          event.getData());
    } catch (SAXException e) {
      throw new XMLStreamException(e);
    }
  }

  private void handleCharacters(Characters event) throws XMLStreamException {
    try {
      _sax.characters(
          event.getData().toCharArray(),
          0,
          event.getData().length());
    } catch (SAXException e) {
      throw new XMLStreamException(e);
    }
  }

  private void handleEndElement(EndElement event) throws XMLStreamException {
    QName qName = event.getName();

    //construct prefix:localName from qName
    String qname = "";
    if (qName.getPrefix() != null && qName.getPrefix().trim().length() != 0) {
      qname = qName.getPrefix() + ":";
    }
    qname += qName.getLocalPart();

    try {
      // fire endElement
      _sax.endElement(
          qName.getNamespaceURI(),
          qName.getLocalPart(),
          qname);

      // end namespace bindings
      for (Iterator i = event.getNamespaces(); i.hasNext(); ) {
        String prefix = (String) i.next();
        if (prefix == null) { // true for default namespace
          prefix = "";
        }
        _sax.endPrefixMapping(prefix);
      }
    } catch (SAXException e) {
      throw new XMLStreamException(e);
    }
  }

  private void handleStartElement(StartElement event)
      throws XMLStreamException {
    try {
      // start namespace bindings
      for (Iterator i = event.getNamespaces(); i.hasNext(); ) {
        String prefix = ((Namespace) i.next()).getPrefix();
        if (prefix == null) { // true for default namespace
          prefix = "";
        }
        _sax.startPrefixMapping(
            prefix,
            event.getNamespaceURI(prefix));
      }

      // fire startElement
      QName qName = event.getName();
      String prefix = qName.getPrefix();
      String rawname;
      if (prefix == null || prefix.length() == 0) {
        rawname = qName.getLocalPart();
      } else {
        rawname = prefix + ':' + qName.getLocalPart();
      }

      Attributes saxAttrs = getAttributes(event);
      _sax.startElement(
          qName.getNamespaceURI(),
          qName.getLocalPart(),
          rawname,
          saxAttrs);
    } catch (SAXException e) {
      throw new XMLStreamException(e);
    }
  }

  /**
   * Get the attributes associated with the given START_ELEMENT StAXevent.
   *
   * @return the StAX attributes converted to an org.xml.sax.Attributes
   */
  private Attributes getAttributes(StartElement event) {
    AttributesImpl attrs = new AttributesImpl();

    if (!event.isStartElement()) {
      throw new InternalError(
          "getAttributes() attempting to process: " + event);
    }

    // in SAX, namespace declarations are not part of attributes by default.
    // (there's a property to control that, but as far as we are concerned
    // we don't use it.) So don't add xmlns:* to attributes.

    // gather non-namespace attrs
    for (Iterator i = event.getAttributes(); i.hasNext(); ) {
      Attribute staxAttr = (javax.xml.stream.events.Attribute) i.next();

      String uri = staxAttr.getName().getNamespaceURI();
      if (uri == null) {
        uri = "";
      }
      String localName = staxAttr.getName().getLocalPart();
      String prefix = staxAttr.getName().getPrefix();
      String qName;
      if (prefix == null || prefix.length() == 0) {
        qName = localName;
      } else {
        qName = prefix + ':' + localName;
      }
      String type = staxAttr.getDTDType();
      String value = staxAttr.getValue();

      attrs.addAttribute(uri, localName, qName, type, value);
    }

    return attrs;
  }

  private void handleNamespace() {
    // no-op ???
    // namespace events don't normally occur outside of a startElement
    // or endElement
  }

  private void handleAttribute() {
    // no-op ???
    // attribute events don't normally occur outside of a startElement
    // or endElement
  }

  private void handleDTD() {
    // no-op ???
    // it seems like we need to pass this info along, but how?
  }

  private void handleComment() {
    // no-op ???
  }

  private void handleEntityReference() {
    // no-op ???
  }

  private void handleSpace() {
    // no-op ???
    // this event is listed in the javadoc, but not in the spec.
  }

  private void handleNotationDecl() {
    // no-op ???
    // this event is listed in the javadoc, but not in the spec.
  }

  private void handleEntityDecl() {
    // no-op ???
    // this event is listed in the javadoc, but not in the spec.
  }

  private void handleCDATA() {
    // no-op ???
    // this event is listed in the javadoc, but not in the spec.
  }


  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public DTDHandler getDTDHandler() {
    return null;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public ErrorHandler getErrorHandler() {
    return null;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public boolean getFeature(String name) throws SAXNotRecognizedException,
      SAXNotSupportedException {
    return false;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void setFeature(String name, boolean value) throws
      SAXNotRecognizedException, SAXNotSupportedException {
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void parse(String sysId) throws IOException, SAXException {
    throw new IOException("This method is not yet implemented.");
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void setDTDHandler(DTDHandler handler) throws NullPointerException {
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void setEntityResolver(EntityResolver resolver) throws
      NullPointerException {
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public EntityResolver getEntityResolver() {
    return null;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void setErrorHandler(ErrorHandler handler) throws
      NullPointerException {
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public void setProperty(String name, Object value) throws
      SAXNotRecognizedException, SAXNotSupportedException {
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public Object getProperty(String name) throws SAXNotRecognizedException,
      SAXNotSupportedException {
    return null;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public int getColumnNumber() {
    return 0;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public int getLineNumber() {
    return 0;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public String getPublicId() {
    return null;
  }

  /**
   * This class is only used internally so this method should never
   * be called.
   */
  public String getSystemId() {
    return null;
  }
}
